package com.xuyanze;


import com.alibaba.fastjson.JSON;
import com.xuyanze.config.properties.ApiDocProperties;
import com.xuyanze.core.ApiDocGenerator;
import com.xuyanze.core.manager.PathManager;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.invoker.*;
import org.springframework.beans.BeansException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.lang.reflect.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

@Mojo(name = "api-doc-generate",requiresDependencyResolution= ResolutionScope.COMPILE)
public class ApiMojo
    extends AbstractMojo{

    @Parameter(defaultValue = "${project}")
    public MavenProject mavenProject;

    @Parameter(name = "apiDocProperties",required = true)
    public ApiDocProperties apiDocProperties;

    private final String DEFAULT_SCAN = "com.xuyanze";

    public void execute()throws MojoExecutionException{
        boolean compile = compile(mavenProject.getFile());
        if(!compile) return;

        URLClassLoader classLoader = getClassLoader();
        Object reflections=null;
        try {
            reflections = getReflections(classLoader,apiDocProperties.getScanPackage() == null ? DEFAULT_SCAN:apiDocProperties.getScanPackage());
            ApiDocGenerator apiDocGenerator = new ApiDocGenerator(apiDocProperties);
            Method getTypesAnnotatedWith = reflections.getClass().getMethod("getTypesAnnotatedWith", Class.class);
            Set<Class<?>> typesAnnotatedWith = (Set<Class<?>>)getTypesAnnotatedWith.invoke(reflections, RestController.class);
            apiDocGenerator.handler(typesAnnotatedWith);
        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 编译
     * @param pom
     * @return
     */
    public boolean compile(File pom){
        InvocationRequest request = new DefaultInvocationRequest();
        request.setPomFile(pom);
        List<String> aList = new ArrayList<>();
        aList.add("clean");
        aList.add("compile");
        request.setGoals(aList);
        Invoker invoker = new DefaultInvoker();
        try {
            InvocationResult execute = invoker.execute(request);
            int exitCode = execute.getExitCode();
            return exitCode == 0;
        } catch (MavenInvocationException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * scan操作句柄
     * @param classLoader
     * @param scanPackage
     * @return
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private Object getReflections(URLClassLoader classLoader,String scanPackage) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Object reflections;
        String[] scannerPackages = {scanPackage};
        Class<?> rClass = classLoader.loadClass("org.reflections.Reflections");
        Constructor<?>[] constructors = rClass.getConstructors();
        Class<?> scannerClass = classLoader.loadClass("org.reflections.scanners.Scanners");
        Class<?> configClass = classLoader.loadClass("org.reflections.util.ConfigurationBuilder");
        Class<?> confClass = classLoader.loadClass("org.reflections.Configuration");

        Object[] scanners = (Object[]) Array.newInstance(scannerClass, 1);
        Object[] enumConstants = scannerClass.getEnumConstants();
        scanners[0]=enumConstants[1];

        Object o1 = configClass.newInstance();
        Method[] configClassDeclaredMethods = configClass.getDeclaredMethods();
        Method forPackage = null;
        Method addScanners = null;
        for (Method method : configClassDeclaredMethods) {
            if(method.getName().contains("forPackage") && method.getParameterCount()==1){
                forPackage = method;
            }
            if(method.getName().contains("addScanners")){
                addScanners = method;
            }
        }

        forPackage.invoke(o1,(Object) scannerPackages);
        addScanners.invoke(o1,(Object)scanners);

        Constructor<?> reflectConstructor = constructors[0];
        for (Constructor<?> constructor : constructors) {
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            if (Objects.isNull(parameterTypes) || parameterTypes.length < 1) {
                continue;
            }
            Class<?> parameterType = parameterTypes[0];
            if (parameterType.equals(confClass)) {
                reflectConstructor = constructor;
                break;
            }
        }

        reflections = reflectConstructor.newInstance(o1);
        return reflections;
    }

    /**
     * 目标classloader
     * @return
     */
    private URLClassLoader getClassLoader() {
        try {
            List<String> compileClasspathElements = mavenProject.getCompileClasspathElements();
            System.out.println(compileClasspathElements);
            compileClasspathElements.add(mavenProject.getBuild().getOutputDirectory());
            compileClasspathElements.add(mavenProject.getBuild().getTestOutputDirectory());
            URL[] urls = new URL[compileClasspathElements.size()];
            for (int i = 0; i < compileClasspathElements.size(); i++) {
                urls[i] = new File(compileClasspathElements.get(i)).toURL();
            }
            return new URLClassLoader(urls,this.getClass().getClassLoader());

        } catch (DependencyResolutionRequiredException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return null;
    }



}
